package com.liyunc.demo.comp.id.generate.service.impl;

import com.liyunc.demo.comp.id.generate.service.SingIdGenerator;
import lombok.extern.slf4j.Slf4j;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

@Slf4j
public class SnowFlakeIdWorker implements SingIdGenerator {

    /**
     * 生成ID.
     */
    @Override
    public Long snowFlake() {
        this.datacenterId = getDatacenterId();
        this.workerId = getMaxWorkerId(datacenterId);
        return this.nextId();
    }

    /**
     * 生成ID.
     *
     * @param workerId     工作机器ID
     * @param datacenterId 序列号
     */
    @Override
    public Long snowFlake(Long workerId, Long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(
                String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException(
                String.format("datacenter Id can't be greater than %d or less than 0",
                    MAX_DATACENTER_ID));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;

        return this.nextId();
    }

    private long workerId;
    // 数据标识id部分
    private long datacenterId;
    // 时间起始标记点，作为基准，一般取系统的最近时间（一旦确定不能变动）
    private static final long TWEPOCH = 1288834974657L;
    // 机器标识位数
    private static final long WORKER_ID_BITS = 5L;
    // 数据中心标识位数
    private static final long DATA_CENTER_ID_BITS = 5L;
    // 机器ID最大值
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    // 数据中心ID最大值
    private static final long MAX_DATACENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
    // 毫秒内自增位
    private static final long SEQUENCE_BITS = 12L;
    // 机器ID偏左移12位
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    // 数据中心ID左移17位
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    // 时间毫秒左移22位
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
        + DATA_CENTER_ID_BITS;
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    /* 上次生产id时间戳 */
    private static long lastTimestamp = -1L;
    // 0，并发控制
    private long sequence = 0L;


    /**
     * 获取下一个ID.
     */
    private synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 当前毫秒内，则+1
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                // 当前毫秒内计数满了，则等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID，并返回ID
        return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT)
            | (datacenterId << DATACENTER_ID_SHIFT)
            | (workerId << WORKER_ID_SHIFT) | sequence;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }


    private static long getMaxWorkerId(long datacenterId) {
        final long number = 0xffff;
        StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & number) % (SnowFlakeIdWorker.MAX_WORKER_ID + 1);
    }


    private static long getDatacenterId() {
        long id = 0L;
        final long number = 0x000000FF;
        final int left = 8;
        final int right = 6;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((number & (long) mac[mac.length - 1])
                    | (number & (((long) mac[mac.length - 2]) << left))) >> right;
            }
        } catch (Exception exception) {
            log.error("get Datacenter Id error", exception);
        }
        return id;
    }

}
